package com.cfs.chat.moudles.server;

import com.cfs.chat.moudles.client.Client;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Description: WebSocket 服务器端点类
 * @Author: fengwei
 * @Date: 2024-11-27 17:27
 **/
@Log4j2
@ServerEndpoint(value = "/websocket")
public class WebSocketServer {

    /** 静态变量，用来记录当前在线连接数，应该是线程安全的 **/
    private static final AtomicInteger onlineCount = new AtomicInteger(0);
    /** 用户和房间号的映射关系 **/
    private static final Map<String, String> roomForUser = Collections.synchronizedMap(new HashMap<>());
    /** 房间和用户集合的映射关系 **/
    public static final Map<String, CopyOnWriteArraySet<Client>> userForRoom = new HashMap<>();
    /** 房间和密码的映射关系 **/
    public static final Map<String, String> pwdForRoom = Collections.synchronizedMap(new HashMap<>());
    /** 用于 JSON 解析的 Gson 实例 **/
    private static final Gson gson = new Gson();
    /** 当前 WebSocket 会话 **/
    private Session session;

    /** 当 WebSocket 连接建立时调用 **/
    @OnOpen
    public void onOpen(Session session) throws IOException {
        // 初始化会话
        this.session = session;
        // 增加在线人数
        addOnlineCount();
        // 记录日志信息
        log.info("用户【{}】连接建立成功", session.getId());
        // 准备返回的信息
        Map<String, String> result = new HashMap<>();
        result.put("type", "bing");
        result.put("sendUser", "系统消息");
        // 设置会话 ID
        result.put("id", session.getId());
        // 发送消息给客户端
        sendMessage(gson.toJson(result));
    }

    /** 当 WebSocket 连接关闭时调用 **/
    @OnClose
    public void onClose() {
        // 减少在线人数
        subOnlineCount();
        // 获取房间内的所有用户
        CopyOnWriteArraySet<Client> clientList = getClientList(session);
        if (CollectionUtils.isNotEmpty(clientList)) {
            // 获取用户昵称
            String nick = getClientNickname(session, clientList);
            // 发送离开房间的系统消息
            sendSystemMessage(clientList, nick + "离开房间");
            // 移除当前会话的用户
            clientList.removeIf(user -> user.getId().equals(session.getId()));
            // 如果房间内没有用户了
            if (clientList.isEmpty()) {
                // 移除房间
                String room = roomForUser.remove(session.getId());
                // 移除房间映射
                userForRoom.remove(room);
                // 移除房间密码
                pwdForRoom.remove(room);
            }
        }
    }

    /** 当收到客户端消息时调用 **/
    @OnMessage
    public void onMessage(String message, Session session) {
        // 解析收到的消息
        Map<String, String> map = gson.fromJson(message, new TypeToken<HashMap<String, String>>() {}.getType());
        // 准备返回的信息
        Map<String, String> result = new HashMap<>();
        String type = map.get("type");
        // 处理用户发送的消息
        if ("msg".equals(type) || "img".equals(type)) {
            handleClientMessage(session, map, result);
        } else if ("init".equals(type)) {
            // 处理初始化消息
            handleClientInit(session, map, result);
        }
        // 发送消息给房间内的所有用户
        sendMessages(getClientList(session), gson.toJson(result), map.get("shiels"));
    }

    /** 当 WebSocket 连接出错时调用 **/
    @OnError
    public void onError(Session session, Throwable error) {
        // 减少在线人数
        subOnlineCount();
        // 记录错误日志
        log.error("连接发生错误：{}", session.getId(), error);
        // 获取房间内的所有用户
        CopyOnWriteArraySet<Client> users = getClientList(session);
        if (users != null) {
            // 获取用户昵称
            String nick = getClientNickname(session, users);
            // 发送离开房间的系统消息
            sendSystemMessage(users, nick + "离开房间");
            // 移除当前会话的用户
            users.removeIf(user -> user.getId().equals(session.getId()));
            if (users.isEmpty()) {
                // 移除房间
                String room = roomForUser.remove(session.getId());
                // 移除房间映射
                userForRoom.remove(room);
                // 移除房间密码
                pwdForRoom.remove(room);
            }
        }
    }

    private void handleClientMessage(Session session, Map<String, String> map, Map<String, String> result) {
        // 获取当前会话的用户
        Client client = getClient(session);
        String type = map.get("type");
        // 设置返回消息的类型
        result.put("type", type);
        // 设置返回消息的内容
        result.put("msg", map.get("msg"));
        // 设置发送用户的昵称
        result.put("sendUser", client.getNickname());
    }

    private void handleClientInit(Session session, Map<String, String> map, Map<String, String> result) {
        String room = map.get("room");
        String nick = map.get("nick");
        String pwd = map.get("pwd");
        // 检查房间号和昵称是否为空
        if (room != null && nick != null) {
            // 创建新用户
            Client client = new Client(session.getId(), nick);
            // 将用户加入房间
            userForRoom.computeIfAbsent(room, k -> new CopyOnWriteArraySet<>()).add(client);
            // 如果房间不存在密码，设置房间密码
            pwdForRoom.putIfAbsent(room, pwd);
            // 记录用户和房间的映射
            roomForUser.put(session.getId(), room);
            result.put("type", "init");
            // 设置返回的初始化消息
            result.put("msg", nick + "成功加入房间");
            result.put("sendUser", "系统消息");
        }
    }

    private String getClientNickname(Session session, CopyOnWriteArraySet<Client> clientList) {
        // 根据会话 ID 查找用户昵称
        return clientList.stream().filter(client -> client.getId().equals(session.getId())).findFirst().map(Client::getNickname).orElse("未知");
    }

    private void sendMessages(CopyOnWriteArraySet<Client> clientList, String message, String shiels) {
        List<String> shielList = StringUtils.isBlank(shiels) ? Collections.emptyList() : Arrays.asList(shiels.split(","));
        // 排除屏蔽的用户
        clientList.stream().filter(user -> !shielList.contains(user.getId())).forEach(user -> {
            try {
                // 发送消息
                user.getWebSocket().sendMessage(message);
            } catch (IOException e) {
                // 记录发送消息错误
                log.error("发送消息错误：", e);
            }
        });
    }

    private void sendSystemMessage(CopyOnWriteArraySet<Client> clientList, String message) {
        Map<String, String> result = new HashMap<>();
        result.put("type", "init");
        // 设置系统消息内容
        result.put("msg", message);
        result.put("sendUser", "系统消息");
        // 发送系统消息
        sendMessages(clientList, gson.toJson(result), null);
    }

    /**  增加在线用户数 **/
    private static void addOnlineCount() {
        // 获取当前值并自增
        onlineCount.getAndIncrement();
    }

    /** 减少在线用户数 **/
    private static void subOnlineCount() {
        // 获取当前值并自减
        onlineCount.getAndDecrement();
    }

    /** 获取客户端 **/
    private CopyOnWriteArraySet<Client> getClientList(Session session) {
        // 获取用户所在的房间
        String room = roomForUser.get(session.getId());
        // 获取房间内的用户集合
        return userForRoom.get(room);
    }

    private Client getClient(Session session) {
        // 获取当前会话的用户
        return getClientList(session).stream().filter(user -> user.getId().equals(session.getId())).findFirst().orElse(null);
    }

    /** 发送文本消息 **/
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

}
